package com.gitee.l0km.codegen.decorator;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.RepeatableContainers;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.io.InputStreamSource;

import com.gitee.l0km.codegen.annotations.AnnotationException;
import com.gitee.l0km.codegen.annotations.AnnotationRuntimeException;
import com.gitee.l0km.codegen.annotations.DeriveMethod;
import com.gitee.l0km.codegen.base.AnnotationHelper;
import com.gitee.l0km.codegen.base.AnnotationUtils;
import com.gitee.l0km.codegen.base.InvalidAnnotationDefineException;
import com.gitee.l0km.codegen.base.InvalidNameException;
import com.gitee.l0km.codegen.base.Method;
import com.gitee.l0km.codegen.base.NewSourceInfoAbstract;
import com.gitee.l0km.codegen.base.ServiceInfo;
import com.gitee.l0km.codegen.base.Method.Parameter;
import com.gitee.l0km.com4j.basex.AnnotationProxy;
import com.gitee.l0km.com4j.basex.ReflectionUtils;
import com.gitee.l0km.com4j.basex.bean.BeanPropertySupport;
import com.google.common.base.Strings;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;

import static com.google.common.base.Preconditions.*;

/**
 * spring web decorator生成器{@code NewSourceInfo}实现
 * @author guyadong
 *
 * @param <T>
 */
public class SpringDecorator<T> extends NewSourceInfoAbstract<T> {
	private static final Logger logger = LoggerFactory.getLogger(SpringDecorator.class);
	private ServiceInfo serviceInfo;
	private String portPrefix;
	private final SpringAnnotationHelper annotationHelper =new SpringAnnotationHelper(); 
	public SpringDecorator(Class<? extends T> implClass) {
		super(null, null, implClass);
		portPrefix = "/" + implClass.getSimpleName() + "/";
	}
	@Override
	public AnnotationHelper getAnnHelper() {
		return annotationHelper;
	}
	@Override
	protected void addImportedClassFromMethod(Method method, 
			boolean importExceptions, boolean importReturn, boolean importAnnotation) {
		// 不需要方法声明的异常类型和返回类型
		super.addImportedClassFromMethod(method, false, false, importAnnotation);
	
		if(importAnnotation) {
			// 将方法中的注释引用的类添加到import class中
			addImportedClass(getAnnHelper().annotationClassOf(method, true, "SUPERCLASS"));
			addImportedClass(getAnnHelper().annotationClassOf(getBaseClass(), "SUPERCLASS"));
		}
	}
	
	@Override
	public boolean compile() {
		boolean compileOk=false;
		try{
			serviceInfo=new ServiceInfo(AnnotationUtils.getServiceAnnotation(baseClass));
			super.compile();
			for (Method method : methodsNeedGenerated) {
				compile(method);
			}
			compileOk = true;
		} catch (AnnotationException e) {
			logger.error(e.toString());
		} catch(AnnotationRuntimeException e){
			logger.error(e.toString());
		} 
		return compileOk;
	}
	private final void compile(Method method) {
		for (Parameter param : method.getParameters()) {
			compile(param);
		}
	}

	private final void compile(Parameter param) {

	}
	@Override
	protected void checkClass(Class<T> interfaceClass, Class<? extends T> refClass, Class<? extends T> baseClass) {
		checkArgument(null != baseClass,"baseClass is null");
	}
	/**
	 * 从当前类型向上返回类型的所有父类(包括自己),输入参数为空返回空表
	 * @param clazz
	 */
	private static List<Class<?>> typeHierarchy(Class<?> clazz){
		ImmutableList.Builder<Class<?>> builder = ImmutableList.builder();
		for(Class<?> cls = clazz; null != cls;cls=cls.getSuperclass()) {
			builder.add(cls);
		}
		return builder.build();
	}
	@Override
	protected void createMethodsNeedGenerated() {
		List<Class<?>> superClasses = typeHierarchy(baseClass);
		// 所有非桥接且非实现接口类方法且有RequestMapping注解的方法
		List<Method> methods = 
				FluentIterable.from(Arrays.asList(baseClass.getMethods()))
					.transform(input->new Method(input,
							paramTable.getParameterNamesUnchecked(input.getName(), input.getParameterTypes())))
					.filter(input->!input.delegate().isBridge()&& !isImplementInterface(input) && isSpringWebPort(input))
					.toList();
		Map<Class<?>, List<Method>> methodsByDeclaringClass 
			= methods.stream().collect(Collectors.groupingBy(Method::getDeclaringClass));
		Map<Class<?>, List<Method>> sortedMethodsByDeclaringClass = Maps.transformValues(methodsByDeclaringClass, l->{
			/**  
			 * 方法按照签名排序,签名中不含返回值类型和所属类，
			 * 因为所有方法是按所属类分组的，所以不影响输出结果 
			 */
			return Ordering.<Method>from((o1,o2)->o1.getSignature().compareTo(o2.getSignature())).sortedCopy(l);
		});
		for(Class<?> clazz : superClasses) {
			 List<Method> sortedMethods =sortedMethodsByDeclaringClass.get(clazz);
			if(null != sortedMethods) {
				methodsNeedGenerated.addAll(sortedMethods);
			}
		}
	}

	public ServiceInfo getServiceInfo() {
		return serviceInfo;
	}
	/**
	 * 判断方法是否定义为Spring Web端口
	 * @param method
	 * @return
	 */
	private boolean isSpringWebPort(Method method) {
		return null == method ? false
				: MergedAnnotations.from(method.delegate(), SearchStrategy.SUPERCLASS, RepeatableContainers.none())
					.isPresent(RequestMapping.class);
	}
	/**
	 * 判断方法是否为实现接口的方法
	 * @param input
	 */
	private boolean isImplementInterface(Method input) {
		Class<?> oc = ReflectionUtils.getOverrideClass(input.delegate());
		return null !=oc && oc.isInterface();
	}
	private DeriveMethod getDeriveMethodAnnotation(Method method) {
		try {
			return AnnotationUtils.getDeriveMethodAnnotation(method, serviceInfo);
		} catch (InvalidNameException e) {
			throw new RuntimeException(e);
		} catch (InvalidAnnotationDefineException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 返回 {@link DeriveMethod} 注释指定的方法名后缀,没有则返回{@code null}
	 * @param method
	 */
	public String methodSuffix(Method method){
		DeriveMethod deriveMethod = getDeriveMethodAnnotation(method);
		if(deriveMethod !=null ){
			String[] suffixs = deriveMethod.methodSuffix();
			return suffixs != null && suffixs.length>0 ? suffixs[0]:"";
		}
		return "";
	}

	public SpringDecorator<T> setExcludeAnnotations(Set<String> excludeAnnotations) {
		getAnnHelper().setExcludeAnnotations(excludeAnnotations);
		return this;
	}
	public SpringDecorator<T> setPortPrefix(String portPrefix) {
		if(!Strings.isNullOrEmpty(portPrefix)) {
			this.portPrefix = portPrefix;
		}
		return this;
	}
	public String requestMethodOf(Method method, String defaultValue){
		return getAnnHelper().requestMethodOf(method,defaultValue);
	}
	public String producesOf(Method method){
		return getAnnHelper().producesOf(method);
	}
	public String appendProducesIfNoempty(Method method){
		return getAnnHelper().appendProducesIfNoempty(method);
	}
	public boolean needResponse(Method method){
		return getAnnHelper().needResponse(method);
	}
	public String defaultValueCodeOf(Parameter parameter,String left){
		return getAnnHelper().defaultValueCodeOf(parameter,left);
	}
	/**
	 * 根据参数类型和参数注释计算参数是否为必须的
	 * @param parameter
	 * @return true if required,otherwise false
	 */
	public boolean isRequired(Parameter parameter) {
		if(null != parameter) {
			if(parameter.getType().isPrimitive()) {
				return true;
			}
			if(null != parameter.getAnnotation("NotNull")) {
				return true;
			}
			if(null != parameter.getAnnotation("Nullable")) {
				return false;
			}
			RequestParam rpa = parameter.getAnnotation(RequestParam.class);
			if(null != rpa){
				return rpa.required();
			}
		}
		return false;
	}
	private boolean isSubType(Parameter parameter,Class<?>superType) {
		if(null != parameter && null  != superType) {
			return(superType.isAssignableFrom(parameter.getType()));
		}
		return false;
	}
	/**
	 * @param parameter
	 * @since 2.2.5
	 */
	public boolean isInputStreamSource(Parameter parameter) {
		return isSubType(parameter,InputStreamSource.class);
	}
	/**
	 * @param method
	 * @since 2.2.5
	 */
	public boolean hasInputStreamSourcePamameter(Method method) {
		return FluentIterable.from(method.getParameters()).anyMatch(this::isInputStreamSource);
	}
	private class SpringAnnotationHelper extends AnnotationHelper{

		@Override
		public AnnotationProxy<? extends Annotation> normalizeAnnotation(Method method, Annotation annot) {
			if(null != method && null != annot && RequestMapping.class.equals(annot.annotationType())){
				@SuppressWarnings({ "rawtypes", "unchecked" })
				AnnotationProxy<RequestMapping> rma = new AnnotationProxy(annot);
				RequestMapping a = rma.createAnnotation();
				if(BeanPropertySupport.isEmpty(a.value()) && BeanPropertySupport.isEmpty(a.path())){
					rma.setValue("value", 
							new String[]{portPrefix + method.getName() + methodSuffix(method)});
				}
				if(BeanPropertySupport.isEmpty(a.method())){
					rma.setValue("method", new RequestMethod[]{RequestMethod.POST});
				}
				return rma;
			}
			return super.normalizeAnnotation(method, annot);
		}
		
	}
}
